﻿using FreeRedis;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        // Redis分布式锁方案方案四：SET的扩展命令（SET EX PX NX）
        // 除了使用，使用Lua脚本，保证SETNX + EXPIRE两条指令的原子性，我们还可以巧用Redis的SET指令扩展参数！
        // SET key value[EX seconds][PX milliseconds][NX|XX]，它也是原子性的！

        // 可能存在问题：
        // 锁被别的线程误删
        // 锁过期释放了，业务还没执行完(可以延长key的过期时间)

        Console.WriteLine("请输入秒杀商品Id：");
        var skuId = Console.ReadLine();

        Console.WriteLine("请输入秒杀商品库存:");
        var skuStockNum = int.Parse(Console.ReadLine() ?? "10");
        RedisClient redisSkuStockClient = new RedisClient("127.0.0.1:32002,defaultDatabase=0");
        var skuStockKey = $"sku:stock:{skuId}";
        redisSkuStockClient.Set<int>(skuStockKey, skuStockNum);

        Console.WriteLine("请输入秒杀数量：");
        var skuNum = int.Parse(Console.ReadLine() ?? "1");

        Console.WriteLine("请输入秒杀并发人数：");
        var userCount = int.Parse(Console.ReadLine() ?? "100");

        var redisKey = $"sale:activity:{skuId}";
        for (int i = 1; i <= userCount; i++)
        {
            var userId = i;
            Thread thread = new Thread(new ThreadStart(() =>
            {
                RedisClient redisSetExClient = new RedisClient("127.0.0.1:32002,defaultDatabase=0");
                var param = new { skuId = skuId, SkuName = $"商品：{skuId}", count = skuNum, userId = userId };
                var paramValue = Newtonsoft.Json.JsonConvert.SerializeObject(param);

                //NX :表示key不存在的时候，才能set成功，也即保证只有第一个客户端请求才能获得锁，而其他客户端请求只能等其释放锁，才能获取。
                //EX seconds :设定key的过期时间，时间单位是秒。
                //PX milliseconds: 设定key的过期时间，单位为毫秒
                //XX: 仅当key存在时设置值
                // https://redis.io/commands/set/
                var obj = redisSetExClient.Set<string>(redisKey, paramValue, TimeSpan.FromSeconds(10), false, true, false, false);
                if (obj == "OK")
                {
                    var skuStock = redisSetExClient.Get<int>(skuStockKey);
                    var newSkuStock = skuStock - skuNum;
                    if (newSkuStock >= 0)
                    {
                        Console.WriteLine($"用户:{userId}，已获取到锁，已生成订单，现库存：{skuStock},下单后库存：{newSkuStock},--------------------");
                        redisSkuStockClient.Set<int>(skuStockKey, newSkuStock);
                        redisSkuStockClient.Del(redisKey);
                    }
                    else
                    {
                        Console.WriteLine($"用户:{userId}，未生成订单，现库存：{skuStock}");
                        redisSkuStockClient.Del(redisKey);
                    }
                }
                else
                {
                    Console.WriteLine($"用户:{userId}，未获取到锁");
                }
            }));
            thread.Start();
        }
    }
}